/*******************************************************************************
filename      IncludeScanner.c
author        Craig Williams

Description:
  Scans all the specifed files (provided from the command line) for header
    dependencies, and then display the results.

  Functions:
    ScanForIncludes   - Main function that scans each file for includes

    AddInclude        - Adds a new file to the includes list
    CleanIncludeList  - Frees all the memory the IncList was useing
    CleanupStats      - Frees all the memory the Stats list was useing
    FileIncluded      - Increment number of times a file was include.
    GetFileStatsIndex - Gets the index of the file in the Stats list.
    Included          - Was the file already included? (Uses IncList)
    PrintIncludes     - Prints out the Includes List
    PrintStats        - Prints out all the file statistics
    PrintTabs         - Prints out the specifed number of tabs
    RedundantInclude  - A file was redundantly included. Increment stats.
*******************************************************************************/
#include "Parser.h"
#include <stdio.h>                      /* printf(), puts(), fputs(), stdout  */
#include <stdlib.h>                     /* malloc(), realloc(), free()        */

#define TAB "    "                      /* Size of Tab charcter               */

static void  AddInclude       (char *file           );
static void  CleanIncludeList (void                 );
static void  CleanupStats     (void                 );
static void  FileIncluded     (char *file           );
static int   GetFileStatsIndex(char *file           );
static int   Included         (char *file           );
static void  PrintIncludes    (void                 );
static void  PrintStats       (void                 );
static void  PrintTabs        (int   tabs           );
static void  RedundantInclude (char *file           );
static void  ScanForIncludes  (char *file, int scope);

static void PrintHelpMessage  (char *name           );

struct Stats_str
{
  char *File             ;              /* Name of the File                   */
  int   TimesIncluded    ;              /* How many times it was included     */
  int   RedundantIncludes;              /* How many times it was included more*/
};                                      /*   then once.                       */

struct Stats_str **Stats = 0;           /* Saves addition file statistics     */
int    NumOfStats   = 0;                /* How many File Stats there are      */

char **IncList      = 0;                /* Stores our list of included files  */
int    NumOfIncs    = 0;                /* How many files have been included  */

int    PrintTree    = 0;                /* Print out includes as a tree?      */
int    PrintErrors  = 0;                /* Print out errors? (File I/O)       */
int    TrackStats   = 0;                /* Track Additional File Stats?       */

/*******************************************************************************
   Function: main()

Description: Sets up our parser, processes all flags, and then scans all the
               specifed files for any includes

     Inputs:   argc - Number of command line arguments
             **argv - Array of strings that was passed in from the command line

    Outputs: 0 - Done
*******************************************************************************/
int main(int argc, char **argv)
{
  int i;

  ParserInit(0, 1024);                  /* Set up our parser. Don't load in a *
                                         *   file, and read in 1KB at a time. */

  AddTokenSeparator(" " , 0, -1, 0, 0, 0); /* Filter out all spaces.          */
  AddTokenSeparator("\"", 0, -1, 0, 0, 0); /* Filter out all quotation marks. */
  AddTokenSeparator("<" , 0, -1, 0, 0, 0); /* Remove all < and > symbols      */
  AddTokenSeparator(">" , 0, -1, 0, 0, 0);
  AddTokenSeparator("\t", 0, -1, 0, 0, 0); /* Remove all tabs                 */
  AddTokenSeparator("\n", 0, -1, 0, 0, 0); /* Remove all new lines            */
  AddTokenSeparator("\r", 0, -1, 0, 0, 0); /* Remove all carrige returns      */

  if(argc == 1)
    PrintHelpMessage(argv[0]);

    /* Check all the command line arguments for any switches. If one is found *
     *   process accordingly and then set it to NULL                          */
  for(i = 1; i < argc; ++i)
  {
    if(Cmp(argv[i], "-h") || Cmp(argv[i], "--help"))  /* Display Help message */
    {
      PrintHelpMessage(argv[0]);

      argv[i] = 0;
    }
    else
    if(Cmp(argv[i], "-s") || Cmp(argv[i], "--stats")) /* Track addition stats */
    {
      TrackStats  = 1;

      argv[i] = 0;
    }
    else
    if(Cmp(argv[i], "-t") || Cmp(argv[i], "--tree"))  /* Display include tree */
    {
      PrintTree = 1;

      argv[i] = 0;
    }
    else
    if(Cmp(argv[i], "-v"))                            /* Verbose output       */
    {
      PrintTree   = 1;
      PrintErrors = 1;
      TrackStats  = 1;

      argv[i] = 0;
    }
  }

  argv[0] = 0;                          /* argv[0] is always the name of the  *
                                         *   program. We have no use for it,  *
                                         *   so set it to null to signify that*
                                         *   we have already processed it.    */

    /* Scan through the arguments again. If it wasn't set to null it should be*
     *   the name of a file to scan for includes.                             */
  for(i = 0; i < argc; ++i)
  {
    if(argv[i])
    {
      if(PrintTree)
        puts(argv[i]);

      ScanForIncludes(argv[i], 0);

      if(!PrintTree)
        PrintIncludes();

      CleanIncludeList();

      printf("\n");
    }
  }

  if(TrackStats)
  {
    PrintStats();
    CleanupStats();
  }

  ParserDeInit();

  return 0;
}

/*******************************************************************************
   Function: PrintHelpMessage()

Description: Prints the help message to the command prompt.

     Inputs: *name - name of the executable.

    Outputs: N/A
*******************************************************************************/
void PrintHelpMessage(char *name)
{
  puts("Include Scanner by Craig Williams, with Parser v 8.9");
  puts("");
  puts("Commands       Effect");
  puts("  -h  --help   Display this message");
  puts("  -s  --stats  Track addition file stats");
  puts("  -t  --tree   Print out the Dependency Tree");
  puts("  -v           Print out the Dependency Tree, addition data, and Error messages");
  puts("");
  puts("Anything else will be considered a file to scan.");
  puts("To scan a entire directory, use *.<extension>");
  puts("");
  puts("Example:");
  printf("  %s -t *.c\n\n", name);
  puts("This will scan each c source file in the current directory, and display the");
  puts("results as a dependency tree");
}

/*******************************************************************************
   Function: ScanForIncludes()

Description: Scans the specified file for any included files, and scan
               any included files for addition includes.

     Inputs: *file  - name of the file to open and scan for includes
              scope - How many tabs to print out before the name, if PrintTree
                      or PrintErrors is enabled.

    Outputs: N/A    - All included files are added to IntList
*******************************************************************************/
void ScanForIncludes(char *file, int scope)
{
  char *token;            /* Holds the token retrieved from GrabToken         */
  int   fpos ;            /* Saves our position in the file.                  */

  AddInclude(file);       /* Add file to the include list, since we are going *
                           *   about to process it                            */

  if(TrackStats)
    FileIncluded(file);

  if(!LoadFile(file))     /* Attempt to open and load in the specifed file.   */
  {                       /*   If it failed, don't do any processing.         */
    if(PrintErrors)
    {
      PrintTabs(scope);

      printf("Could not find %s\n", file);
    }

    return;
  }

  if(!Seek("#include"))   /* Scan the file for a "#include"                   */
  {
    if(PrintErrors)       /* "#include" was not found. Don't process anything */
    {
      PrintTabs(scope);
      printf("No Includes\n");
    }
    return;
  }

    /* This should return the name of a file, since we have set up the parser *
     *   to filter out all white spaces and standard syntax characters        */
  token = GrabToken();

  while(token)            /* Loop until we don't find a file name             */
  {
    if(PrintTree)
    {
      PrintTabs(scope);
      puts(token);
    }

    if(Included(token))   /* Was the file already included?                   */
    {
      if(TrackStats)      /* Yes, so we don't want to process it again.       */
        RedundantInclude(token);

      free(token);

      if(PrintErrors)
      {
        PrintTabs(scope + 1);
        puts("Already Included");
      }
    }
    else
    {
        /* The file has not been processed yet. Since this file inherits all  *
         *   the includes of the included file, we have to process it as well.*
         *   To process the file, we call this function. Since this function  *
         *   will open up the new included file, we have to save the current  *
         *   state of our parser (file postion), and then restore it after    *
         *   we have processed everything.                                    */

      fpos = GetFilePosition();          /* Save our current file position    */
      ScanForIncludes(token, scope + 1); /* Process the new file              */
      LoadFile(file);                    /* Load our original file            */
      SetFilePosition(fpos);             /* Restore our file position         */
    }

    if(Seek("#include"))                 /* Are there any more #includes?     */
      token = GrabToken();               /* Get the name of the new included  */
    else
      token = 0;                         /* Break out of the loop             */
  }

  return;
}

/*******************************************************************************
   Function: AddInclude()

Description: Adds a new file to the Include List.

     Inputs: *file - File to add to the list

    Outputs: N/A   - File name is added to the IncList
*******************************************************************************/
void AddInclude(char *file)
{
  NumOfIncs++;

  IncList = (char **)realloc(IncList, sizeof(char **) * NumOfIncs);

  IncList[NumOfIncs - 1] = file;

  return;
}

/*******************************************************************************
   Function: CleanIncludeList()

Description: Removes all the files name from the IncList

     Inputs: N/A

    Outputs: N/A - All data used by IncList is freeed
*******************************************************************************/
void CleanIncludeList(void)
{
  int i;

    /* Start at 1, since the first thing on the list will be the file passed  *
     * in from the command prompt. IE, we shouldn't free it                   */
  for(i = 1; i < NumOfIncs; ++i)
    free(IncList[i]);

  free(IncList);

  IncList = 0;

  NumOfIncs = 0;

  return;
}

/*******************************************************************************
   Function: CleanupStats()

Description: Removes all the data used Stats

     Inputs: N/A

    Outputs: N/A - Frees all the data Stats was useing.
*******************************************************************************/
void CleanupStats(void)
{
  int i;

  for(i = 0; i < NumOfStats; ++i)
  {
    free(Stats[i]->File);
    free(Stats[i]);
  }

  free(Stats);

  Stats = 0;

  NumOfStats = 0;

  return;
}

/*******************************************************************************
   Function: FileIncluded()

Description: A File was included. Add it to the Stats list if it wasn't included
               before. If it was, increment the total number of times the said
               file was included.

     Inputs: *file - Name of the file to add/update the stats of.

    Outputs: N/A   - Stats[<File Index>] is updated or created.
*******************************************************************************/
void FileIncluded(char *file)
{
  int i = GetFileStatsIndex(file);    /* Get the index of the file in Stats   */

  if(i != -1)
    Stats[i]->TimesIncluded++;        /* Entry exits. Just Inc TimesIncluded  */
  else
  {                                   /* Entry doesnot exits. Make a new one  */
    Stats = (struct Stats_str **)realloc(Stats,
                                 sizeof(struct Stats_str *) * (NumOfStats + 1));
    Stats[NumOfStats] = (struct Stats_str *)malloc(sizeof(struct Stats_str));
    Stats[NumOfStats]->File              = Dup(file); /* Copy the file name   */
    Stats[NumOfStats]->TimesIncluded     = 1        ; /* It was just included */
    Stats[NumOfStats]->RedundantIncludes = 0        ; /* No Redundant includes*/
    NumOfStats++;
  }

  return;
}

/*******************************************************************************
   Function: GetFileStatsIndex()

Description: Gets the index of the specifed file name in the Stats list.

     Inputs: *file - File to search for in Stats

    Outputs: -1 - File was not found
             0+ - Index of the file in Stats
*******************************************************************************/
int GetFileStatsIndex(char *file)
{
  int i;

  for(i = 0; i < NumOfStats; ++i)
    if(Cmp(Stats[i]->File, file))
      return i;

  return -1;
}

/*******************************************************************************
   Function: Included()

Description: Checks the IncList, to see if the file was previsouly included

     Inputs: *file - File name to search for

    Outputs: 1 - File was already included
             0 - File has not been included yet
*******************************************************************************/
int Included(char *file)
{
  int i;

  for(i = 0; i < NumOfIncs; ++i)
    if(Cmp(IncList[i], file))
      return 1;

  return 0;
}

/*******************************************************************************
   Function: PrintIncludes()

Description: Prints out all the data in IncList to the screen
               <Original File>
                  <Included File #1>
                  <Included File #2>
                  <ect...>

     Inputs: N/A

    Outputs: N/A - Contents of IncList is dumped to the screen.
*******************************************************************************/
void PrintIncludes(void)
{
  int i;

  if(!IncList)
    return;

  puts(IncList[0]);

  for(i = 1; i < NumOfIncs; ++i)
    printf("%s%s\n", TAB, IncList[i]);

  return;
}

/*******************************************************************************
   Function: PrintStats()

Description: Dumps addition file statistics to the screen.
              <File Name>
                  Included : <Number of times included>
                  Redundant: <Number of times included more then once>

     Inputs: N/A

    Outputs: N/A - Addition file stats is dumped to the screen.
*******************************************************************************/
void PrintStats(void)
{
  int i;

  printf("Number of Files Scanned: %d\n\n", NumOfStats);

  for(i = 0; i < NumOfStats; ++i)
  {
    puts(Stats[i]->File);
    printf("%sIncluded : %d\n", TAB, Stats[i]->TimesIncluded);
    printf("%sRedundant: %d\n", TAB, Stats[i]->RedundantIncludes);
    printf("\n");
  }

  return;
}

/*******************************************************************************
   Function: PrintTabs()

Description: Prints out the requested number of tabs to the screen.

     Inputs: tabs - How many tabs to print out to the screen

    Outputs: N/A  - The specifed number of tabs is printed to the screen.
*******************************************************************************/
void PrintTabs(int tabs)
{
  for(++tabs; tabs; --tabs)
    printf(TAB);

  return;
}

/*******************************************************************************
   Function: RedundantInclude()

Description: The Specifed file was already included. IE, it was a redundant
               included. Incrment its stats.

     Inputs: *file - Name of the file that was redundantly included

    Outputs: N/A   - Stats for the file is updated
*******************************************************************************/
void RedundantInclude(char *file)
{
  int i = GetFileStatsIndex(file);

  if(i != -1)
    Stats[i]->RedundantIncludes++;

  return;
}
